// * 透明缓存
function slow(x) {
	/* 这里可能有大量密集型计算工作 */
	console.log(`Called with ${x}`)
	return x
}

function cachingDecorator(func) {
	// func == slow（存储在当前词法环境）
	let cache = new Map()

	return function (x) {
		if (cache.has(x)) {
			return cache.get(x)
		}

		let result = func(x)

		cache.set(x, result)
		return result
	}
}

slow = cachingDecorator(slow) // 这个 slow 已经是 cachingDecorator 返回的匿名函数了。

console.log(slow(1)) // slow(1)被缓存下来，并返回结果。
console.log('Again:' + slow(1)) // 第二次想同的调用，返回 slow(1) 缓存的结果。

console.log(slow(2)) // slow(2)被缓存下来，并返回结果。
console.log('Again:' + slow(2)) // 第二次想同的调用，返回 slow(2) 缓存的结果.

// **********************************************************************************************************

// * 使用 "func.call" 设定上下文（上面的缓存装饰器不适用于对象方法）

// 我们将对 worker.slow 的结果进行缓存
let worker = {
	someMethod() {
		return 1
	},

	slow(x) {
		/* 大型密集型计算任务 */
		console.log('Called with ' + x)
		return x + this.someMethod()
	},
}

function cachingDecoratorT(func) {
	// func == worker.slow（存储在当前词法环境）
	let cache = new Map()

	return function (x) {
		if (cache.has(x)) {
			return cache.get(x)
		}

		// this == worker（这个匿名函数是被 worker.slow() 进行调用的，所以这个函数内部的 this 指向的是 worker 对象）
		let result = func.call(this, x)
		cache.set(x, result)
		return result
	}
}

console.log(worker.slow(1)) // 2
worker.slow = cachingDecoratorT(worker.slow) // 现在对 worker.slow 进行缓存。现在 worker.slow 是 cachingDecoratorT 中返回匿名函数。
console.log(worker.slow(2)) // 3

// **********************************************************************************************************

// * call（给运行时的函数指定this）

function say(phrase) {
	console.log(this.name + ' says: ' + phrase)
}

let obj = { name: 'Obj' }

say.call(obj, 'Hello') // Obj says: Hello（第一个参数是指定的this，第二参数是函数接收的参数）

// **********************************************************************************************************

// * 传入多个参数的装饰器函数
let workers = {
	slow(min, max) {
		console.log(`Called with ${min} and ${max}`)
		return min + max
	},
}

function cachingDecorators(func, hash) {
	// func == workers.slow（存储在当前词法环境）
	// hash == 自定义的 hash 函数（存储在当前词法环境）
	let cache = new Map()

	return function () {
		// this == workers

		let key = hash(arguments)
		if (cache.has(key)) {
			return cache.get(key)
		}

		let result = func.call(this, ...arguments)

		cache.set(key, result)
		return result
	}
}

function hash(args) {
	return Array.from(args).join(',')
}

// workers.slow == cachingDecorators（装饰器函数）返回的匿名函数。
workers.slow = cachingDecorators(workers.slow, hash)

console.log(workers.slow(1, 2)) // 3
console.log('Again ' + workers.slow(1, 2)) // 3（缓存中取出）

// **********************************************************************************************************

// * func.apply。
// * call 和 apply 之间唯一的语法区别是，call 期望一个“参数列表”，而 apply 期望一个包含这些参数的“类数组”或数组。
// * call 和 apply 作用都是在函数运行时，指定 this 的值。
// 一个真正的数组，使用 call 或 apply 均可，但是 apply 可能会更快，因为大多数 JavaScript 引擎在内部对其进行了优化。

// func.apply(this, { 0: 1, 1: 2, length: 2 }); apply 只接受 类数组对象 或 数组。
// func.call(this, ...arguments); Spread语法（扩展运算符） ... 允许将 可迭代对象 args 作为列表传递给 call。

// * 使用 apply 传递的参数，接收时会被展开为独立的参数。
function greet(greeting, name, name2) {
	console.log(`${greeting}, ${name}! ${name2}!`)
}

const args = ['Hello', 'Alice', 'Bob']
let arrObjs = { 0: 'Hello', 1: 'Alice', 2: 'Ben', length: 3 }

greet.apply(null, args) // 输出: Hello, Alice! Bob!
greet.apply(null, arrObjs) // 输出: Hello, Alice! Ben!
